#include "BigNumber.h"
#include "utils.h"
#include <sstream>
#include <iomanip>
#include <vector>
using namespace std;


BigNumber::BigNumber(const std::string& str): _str(str)
{
}

int BigNumber::compareTo(const BigNumber& another) const
{
	// remove trailing and leading zeroes
	string s1 = trimLeft(_str, '0');
	string s2 = trimLeft(another._str, '0');

	if ((s1 == "") || (s1 == ".")) s1 = "0";
	if ((s2 == "") || (s2 == ".")) s2 = "0";

	// adjust
	int size = max(s1.length(), s2.length());
	s1 = padLeft(s1, size, '0');
	s2 = padLeft(s2, size, '0');

	return s1 < s2
			? -1
			: s1 == s2
				? 0
				: 1;
}

bool BigNumber::operator<(const BigNumber& another) const
{
	return compareTo(another) == -1;
}

bool BigNumber::operator>(const BigNumber& another) const
{
	return compareTo(another) == 1;
}

bool BigNumber::operator<=(const BigNumber& another) const
{
	int cmp = compareTo(another);
	return (cmp == -1) || (cmp == 0);
}

bool BigNumber::operator>=(const BigNumber& another) const
{
	int cmp = compareTo(another);
	return (cmp == 1) || (cmp == 0);
}

bool BigNumber::operator==(const BigNumber& another) const
{
	return compareTo(another) == 0;
}

bool BigNumber::operator!=(const BigNumber& another) const
{
	return compareTo(another) != 0;
}

BigNumber BigNumber::subtract(const BigNumber& another) const
{
	int size = max(_str.length(), another._str.length());

	string s1 = padLeft(_str, size, '0');
	string s2 = padLeft(another._str, size, '0');
	string result = padLeft("", size, '0');

	for (int i = size - 1; i >= 0; --i)
	{
		int difference = s1[i] - s2[i];

		if (difference < 0)
		{
			--s1[i - 1];
			difference += 10;
		}

		result[i] = toChar(difference);
	}
	return result;
}

BigNumber BigNumber::multiply(int digit) const
{
	if ((digit < 0) || (digit > 10))
	{
		throw std::exception("digit in multiply(digit) should be between 0 and 9");
	}

	if (digit == 10) // minor optimization
	{
		return BigNumber(_str + '0');
	}

	int size = _str.length();
	string s = _str;
	string result = _str;

	int memory = 0;

	for (int i = size - 1; i >= 0; --i)
	{
		int mult = toInt(s[i]) * digit + memory;

		if (mult >= 10)
		{
			memory = mult / 10;
			mult %= 10;
		}
		else
		{
			memory = 0;
		}

		result[i] = toChar(mult);
	}

	if (memory != 0)
	{
		result = toChar(memory) + result;
	}

	return result;
}

BigNumber BigNumber::x10(int n) const // returns number multiplied by 1e+n
{
	if (n == 0)
	{
		return _str;
	}

	return padRight(_str, _str.length() + n, '0');
}

BigNumber BigNumber::divide(const BigNumber& divisor) const
{
	const int maxDepth = 30;
	string quotient = "0";	
	BigNumber dividend = _str;
	bool dotAdded = false;
	
	do
	{
		// find part that can be divided fully by divisor
		string str = "0";

		for (int i = 1; BigNumber(str = dividend._str.substr(0, i)) < divisor; ++i)
		{
			if (i > dividend._str.length())
			{
				// multiply dividend by 10
				dividend = dividend.x10();

				// add . or 0 to quotient
				quotient += (dotAdded++ ? '0' : '.');
			}
		}

		BigNumber currentDividend = BigNumber(str);

		// divide it by the divisor
		int multiplier;
		for (multiplier = 0;  divisor * multiplier <= currentDividend; ++multiplier);
		--multiplier;

		// push that to quotient
		quotient += toChar(multiplier);

		// subtract it multiplied and shifted from dividend
		BigNumber toSubtract = divisor * multiplier;

		// shift appropriately
		toSubtract = padLeft(toSubtract._str, currentDividend._str.length(), '0');
		toSubtract = padRight(toSubtract._str, dividend._str.length(), '0');

		// update dividend
		dividend -= toSubtract;
		if (dotAdded)
		{
			dividend = dividend.x10();
		}
	}
	while ((dividend != BigNumber("0")) && (quotient.length() < maxDepth));

	return quotient;
}

BigNumber BigNumber::operator-(const BigNumber& another) const
{
	return subtract(another);
}

BigNumber BigNumber::operator*(int digit) const
{
	return multiply(digit);
}

BigNumber BigNumber::operator/(const BigNumber& another) const
{
	return divide(another);
}

std::string BigNumber::toString() const
{
	return _str;
}

BigNumber& BigNumber::operator-=(const BigNumber& another)
{
	BigNumber temp = subtract(another);
	_str = temp._str;
	return *this;
}

BigNumber& BigNumber::operator/=(const BigNumber& another)
{
	BigNumber temp = divide(another);
	_str = temp._str;
	return *this;
}

std::ostream& operator <<(std::ostream& stream, const BigNumber& bigNumber)
{
	return stream << bigNumber.toString();
}